package com.wu.demo.farmework.aspect;

import com.wu.demo.farmework.manager.RequestCounter;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;

/**
 * @Auther: wu
 * @Date: 2024/3/10
 * @Description: 计算请求时间的切人程序
 */
@Slf4j
@Aspect
@Component
public class RequestTimeAspect {
    @Resource
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    //实现方式一，此方式默认会匹配controller包下所有含有Controller后缀类的方法
    //实现具体的方法 及 切点表达式
    /*@Around("execution(* com.wu.demo.controller.*Controller.*(..))")
    public Object requestExecuteTime(ProceedingJoinPoint point){
        long start = System.currentTimeMillis();
        Object result = null;//目标方法的返回值
        try {
            //执行具体的目标方法
            result = point.proceed();
        } catch (Throwable e) {
            log.error("执行失败",e);
            throw new RuntimeException(e);
        }
        long end = System.currentTimeMillis();
        //获取目标方法类名和方法名称
        log.info("请求方法-》{}.{}",point.getSignature().getDeclaringTypeName(),point.getSignature().getName());
        log.info("消耗时间----"+(end-start));
        return result;
    }*/
    //---------------------------------------------------------------------------------
    //方式2 基于注解实现，方式灵活多样，同时可扩展实现request的统计 如统计某段时间内request的请求次数，最大处理时间等
    private static ThreadLocal<Long> startTime = new ThreadLocal<>();

    /**
     * 切入点：通过自定义注解标记需要监控的request接口
     */
    @Pointcut("@annotation(com.wu.demo.common.annotation.RequestTime)")
    public void annotationPointCut(){}

    /**
     * 切入点：会匹配com.wu.demo.controller包下所有含有Controller后缀类里的方法
     */
    @Pointcut("execution(* com.wu.demo.controller.*Controller.*(..))")
    public void defaultPointCut(){}

    /**
     * 前置通知 ，满足annotationPointCut与defaultPointCut其中一个就执行
     * @param point
     */
    @Before("annotationPointCut() || defaultPointCut()")
    public void doBefore(JoinPoint point)  {
        startTime.set(System.currentTimeMillis());
        Object[] args = point.getArgs(); //目标方法的参数

        CompletableFuture.runAsync(()->{ //异步执行
            //获取目标方法类名和方法名称
            log.info("doBefore请求方法-》{}.{}",point.getSignature().getDeclaringTypeName(),point.getSignature().getName());
        },threadPoolTaskExecutor);

    }

    //@After（不管目标方法有无异常，都会执行,相当于@around中的finally中执行的模块）
    //执行顺序@Before ——> @AfterReturning/@AfterThrowing ——> @After
    /**
     * 后置通知，@AfterReturning：在切入点正常运行结束后执行，异常则不执行
     * @param point
     * @param returnVal 目标方法返回值
     */
    @Async("threadPoolExecutor")
    @AfterReturning(returning = "returnVal",pointcut = "annotationPointCut() || defaultPointCut()")
    public void doAfterReturning(JoinPoint point,Object returnVal){
        HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
        //计算耗时
        long successTime = System.currentTimeMillis() - startTime.get();
        String key = RequestCounter.getTimeStamp() + "--" + request.getRequestURI();
        RequestCounter.increaseSuccessInfo(key,successTime);
        log.info("----doAfterReturning---请求成功：Url:{},参数：{},耗时:{}ms",request.getRequestURI(),Arrays.toString(point.getArgs()),successTime);
        startTime.remove();
       // return returnVal;
    }

    @After("annotationPointCut() || defaultPointCut()")
    public void after(JoinPoint point){
        log.info("-------------after----------------------");
    }

    /**
     * 异常通知
     * @param ex 异常信息
     * @AfterThrowing：目标方法执行异常时执行
     */
    @Async("threadPoolExecutor")
    @AfterThrowing(throwing = "ex",pointcut = "annotationPointCut() || defaultPointCut()")
    public void doAfterThrowing(JoinPoint point,Exception ex){
        HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
        //计算耗时
        long failTime = System.currentTimeMillis() - startTime.get();
        String key = RequestCounter.getTimeStamp() + "--" + request.getRequestURI();
        RequestCounter.increaseFailInfo(key,failTime);
        log.info("----doAfterThrowing---请求失败：Url:{},参数{}，耗时:{}ms",request.getRequestURI(), Arrays.toString(point.getArgs()) ,failTime);
        startTime.remove();
    }
}
